﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Collections.ObjectModel;
using System.Windows.Media;
using System.ComponentModel;
using System.Reflection;

namespace PSE.Framework.UI.Controls.SmartClient
{
    public class ControlHelper
    {
        #region Construtor
        /// <summary>
        /// Construtor padrão
        /// </summary>
        protected ControlHelper()
        {
        }
        #endregion

        #region FindParent
        /// <summary>
        /// Busca o pai de um objeto com um determinado type
        /// </summary>
        /// <param name="control">Objeto onde a busca inicia</param>
        /// <param name="type">Type do pai</param>
        /// <returns>Pai do objeto se encontrar, null se não encontrar</returns>
        public static DependencyObject FindParent(DependencyObject element, Type type)
        {
            if (element == null)
                throw new ArgumentNullException("element");
            if (type == null)
                throw new ArgumentNullException("type");

            return FindParent(element,
                        new Predicate<DependencyObject>(
                            delegate(DependencyObject obj)
                            {
                                return type.IsInstanceOfType(obj);
                            }
                        )
                    );
        }

        /// <summary>
        /// Busca o pai de um objeto com um determinado nome
        /// </summary>
        /// <param name="control">Objeto onde a busca inicia</param>
        /// <param name="name">Nome do pai</param>
        /// <returns>Pai do objeto se encontrar, null se não encontrar</returns>
        public static DependencyObject FindParent(DependencyObject element, string name)
        {
            if (element == null)
                throw new ArgumentNullException("element");
            if (string.IsNullOrEmpty(name))
                throw new ArgumentNullException("name");

            return FindParent(element, 
                        new Predicate<DependencyObject>(
                            delegate(DependencyObject obj) 
                            {
                                return ((string)obj.GetValue(FrameworkElement.NameProperty) == name);
                            } 
                        )
                    );
        }

        /// <summary>
        /// Busca o pai de um objeto com uma determinada condição
        /// </summary>
        /// <param name="control">Objeto onde a busca inicia</param>
        /// <param name="match">Condição de busca</param>
        /// <returns>Pai do objeto se encontrar, null se não encontrar</returns>
        public static DependencyObject FindParent(DependencyObject element, Predicate<DependencyObject> match)
        {
            if (element == null)
                throw new ArgumentNullException("element");
            if (match == null)
                throw new ArgumentNullException("match");

            DependencyObject parent = VisualTreeHelper.GetParent(element);

            if (parent == null)
            {
                return null;
            }
            if (match(parent))
            {
                return parent;
            }
            else
            {
                return FindParent(parent, match);
            }
        }
        #endregion

        #region FindChild
        /// <summary>
        /// Busca o filho de um objeto com um determinado type
        /// </summary>
        /// <param name="control">Objeto onde a busca inicia</param>
        /// <param name="type">Type do filho</param>
        /// <returns>Filho do objeto se encontrar, null se não encontrar</returns>
        public static DependencyObject FindChild(DependencyObject element, Type type)
        {
            if (element == null)
                throw new ArgumentNullException("element");
            if (type == null)
                throw new ArgumentNullException("type");

            return FindChild(element,
                        new Predicate<DependencyObject>(
                            delegate(DependencyObject obj)
                            {
                                return type.IsInstanceOfType(obj);
                            }
                        )
                    );
        }

        /// <summary>
        /// Busca o filho de um objeto com um determinado nome
        /// </summary>
        /// <param name="control">Objeto onde a busca inicia</param>
        /// <param name="name">Nome do filho</param>
        /// <returns>Filho do objeto se encontrar, null se não encontrar</returns>
        public static DependencyObject FindChild(DependencyObject element, string name)
        {
            if (element == null)
                throw new ArgumentNullException("element");
            if (string.IsNullOrEmpty(name))
                throw new ArgumentNullException("name");

            return FindChild(element,
                        new Predicate<DependencyObject>(
                            delegate(DependencyObject obj)
                            {
                                return ((string)obj.GetValue(FrameworkElement.NameProperty) == name);
                            }
                        )
                    );
        }

        /// <summary>
        /// Busca o filho de um objeto com uma determinada condição
        /// </summary>
        /// <param name="control">Objeto onde a busca inicia</param>
        /// <param name="match">Condição de busca</param>
        /// <returns>Filho do objeto se encontrar, null se não encontrar</returns>
        public static DependencyObject FindChild(DependencyObject element, Predicate<DependencyObject> match)
        {
            if (element == null)
                throw new ArgumentNullException("element");
            if (match == null)
                throw new ArgumentNullException("match");

            int childrenCount = VisualTreeHelper.GetChildrenCount(element);

            for (int i = 0; i < childrenCount; i++)
            {
                DependencyObject child = VisualTreeHelper.GetChild(element, i);

                if (match(child))
                {
                    return child;
                }
                else
                {
                    child = FindChild(child, match);
                    if (child != null)
                    {
                        return child;
                    }

                }
            }

            return null;
        }
        #endregion
        
        #region GetContainer
        /// <summary>
        /// Captura container do objeto, que primeiro objeto pai que implementa IView
        /// </summary>
        /// <param name="element">Objeto onde será capturado o container</param>
        /// <returns>Container do objeto</returns>
        //public static IBaseView GetContainer(DependencyObject element)
        //{
        //    IBaseView container = (IBaseView)FindParent(element, typeof(IBaseView));

        //    if (container == null)
        //        throw new InvalidOperationException("Container not found. The element might not be loaded, is not connected to visual tree or the container doesn't implement IView");

        //    return container;
        //}

        #endregion

        #region GetDependencyProperties

        /// <summary>
        /// Captura somente as attached properties do objeto
        /// </summary>
        /// <param name="element">Objeto de onde as properties serão capturadas</param>
        /// <returns>Properties encontradas</returns>
        public static ReadOnlyCollection<DependencyProperty> GetAttachedProperties(DependencyObject element)
        {
            return GetDependencyProperties(element, true);
        }

        /// <summary>
        /// Captura somente as dependency properties do objeto
        /// </summary>
        /// <param name="element">Objeto de onde as properties serão capturadas</param>
        /// <returns>Properties encontradas</returns>
        public static ReadOnlyCollection<DependencyProperty> GetDependencyProperties(DependencyObject element)
        {
            return GetDependencyProperties(element, false);
        }

        /// <summary>
        /// Captura as dependency e attached properties do objeto
        /// </summary>
        /// <param name="element">Objeto de onde as properties serão capturadas</param>
        /// <returns>Properties encontradas</returns>
        public static ReadOnlyCollection<DependencyProperty> GetAllDependencyProperties(DependencyObject element)
        {
            return GetDependencyProperties(element, null);
        }

        /// <summary>
        /// Captura somente as dependency, somente as attached ou todas as properties do objeto
        /// </summary>
        /// <param name="element">Objeto de onde as properties serão capturadas</param>
        /// <param name="attached">
        /// true retorna somente as as attached properties, 
        /// false retorna somente as as dependency properties ou 
        /// null retorna as dependency e attached properties
        /// </param>
        /// <returns>Properties encontradas</returns>
        private static ReadOnlyCollection<DependencyProperty> GetDependencyProperties(DependencyObject element, bool? attached)
        {
            List<DependencyProperty> properties = new List<DependencyProperty>();

            foreach (PropertyDescriptor pd in TypeDescriptor.GetProperties(element,
                new Attribute[] { new PropertyFilterAttribute(PropertyFilterOptions.All) }))
            {
                DependencyPropertyDescriptor dpd =
                    DependencyPropertyDescriptor.FromProperty(pd);

                if (dpd != null &&
                     (
                       (attached == null) ||
                       (attached == true && dpd.IsAttached) ||
                       (attached == false && !dpd.IsAttached)
                     )
                   )
                {
                    properties.Add(dpd.DependencyProperty);
                }
            }

            // Capturando dependency properties não públicas, não captura attached properties
            foreach (FieldInfo field in element.GetType().GetFields(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy))
            {
                if (field.FieldType == typeof(DependencyProperty))
                {
                    properties.Add((DependencyProperty)field.GetValue(element));
                }
            }

            return properties.AsReadOnly();
        }

        #endregion
    }
}
